package com.ai.opc;

import com.ai.common.constant.OpcConstant;
import com.ai.service.made.IOpcNodeInfoService;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.UaClient;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
import org.eclipse.milo.opcua.sdk.client.subscriptions.OpcUaSubscription;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author jiaok
 * @Date 2024-11-14 14:12
 */
@Slf4j
public class DynamicOpcNodeSubscriber {

    private UaSubscription subscription;
    private IOpcNodeInfoService iOpcNodeInfoService;
    private ConcurrentHashMap<String, DataValue> monitoredItemsMap = new ConcurrentHashMap<>();

    public DynamicOpcNodeSubscriber(UaClient client,IOpcNodeInfoService iOpcNodeInfoService) throws Exception {
        subscription = client.getSubscriptionManager().createSubscription(1000.0).get();
        this.iOpcNodeInfoService = iOpcNodeInfoService;
    }

//    public DynamicOpcNodeSubscriber(UaSubscription subscription,IOpcNodeInfoService iOpcNodeInfoService) throws Exception {
//        this.subscription = subscription;
//        this.iOpcNodeInfoService = iOpcNodeInfoService;
//    }

    /**
     * 添加并订阅节点监控
     * @param nodeId 需要监控的节点 NodeId
     */
    public void addMonitoredItem(NodeId nodeId) {
        ReadValueId readValueId = new ReadValueId(nodeId, AttributeId.Value.uid(), null, null);
        /**
         * 客户端指定监控项的 ID （默认即可）
         * 读取数据的频率
         * 过滤器，筛选数据
         * 缓存队列，当消费端消费不过来的数据会被放置到队列中，
         * 丢弃最旧的值，  （当队列塞满了，前面的数据会出队，被抛弃）
         */
        MonitoringParameters parameters = new MonitoringParameters(UInteger.valueOf(1), 3000.0, null, UInteger.valueOf(10), true);
        MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters);

        List<MonitoredItemCreateRequest> requests = new ArrayList<>();
        requests.add(request);

        subscription.createMonitoredItems(
                TimestampsToReturn.Both,
                requests,
                (monitoredItem, id) -> {
                    monitoredItem.setValueConsumer((item, value) -> {
                        // TODO: 2024-11-14 读取数据异常的场景后期填补，先走完正常的全流程
                        log.info("===== NodeId ===== :" + item.getReadValueId().getNodeId());
                        log.info("===== New Value ===== :" + value.getValue().getValue());
                        iOpcNodeInfoService.handleNodeValue(item.getReadValueId().getNodeId(), (String) value.getValue().getValue().toString());
                    });
                }
        );
    }

    /**
     * 获取节点的最新值
     * @param nodeId 需要获取值的节点 NodeId
     * @return DataValue 节点的最新值
     */
    public DataValue getLatestValue(NodeId nodeId) {
        return monitoredItemsMap.get(nodeId.getIdentifier().toString());
    }

    /**
     * 移除指定正在监听的节点
     * @param nodeId 节点信息
     */
    public void removeMonitoredItem(NodeId nodeId) {
        // TODO: 2024-11-15 移除元素之后，缓存中的值应该设为多少
        UaMonitoredItem uaMonitoredItem = subscription.getMonitoredItems().stream()
                .filter(i -> i.getReadValueId().getNodeId().equals(nodeId))
                .findFirst().orElse(null);
        if (uaMonitoredItem != null) {
            subscription.deleteMonitoredItems((List<UaMonitoredItem>) uaMonitoredItem);
            log.info("删除监控项: " + nodeId);
        }
    }

    /**
     * 移除所有正在监听的节点
     * @throws Exception
     */
    public void stop() throws Exception {
        // TODO: 2024-11-15 节点监控删除之后，redis中的缓存值应该怎么做，设为多少 
        subscription.deleteMonitoredItems(new ArrayList<>(subscription.getMonitoredItems())).get();
        monitoredItemsMap.clear();
        log.info("所有监控项已删除");
    }


}
